﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EuroManager.Domain.Simulator.Engine;
using EuroManager.Domain.Simulator.Helpers;
using EuroManager.Domain.Simulator.Tactics;
using EuroManager.Domain.Simulator.World;
using NUnit.Framework;
using Rhino.Mocks;

namespace EuroManager.Domain.Simulator.Tests.World
{
    [TestFixture]
    public class PlayerTests
    {
        private FakeGameWorld world;
        private Player player;
        
        [SetUp]
        public void SetUp()
        {
            world = new FakeGameWorld(new Vector2D(40, 20));
            player = world.AddPlayer(world.Team1, new Vector2D(30, 20));
        }

        [Test]
        public void ShouldExposeTacticalLocation()
        {
            Vector2D tacticalLocation = new Vector2D(13, 8);
            IPlayerTactics tactics = MockRepository.GenerateStub<IPlayerTactics>();
            tactics.Stub(t => t.CalculateTacticalLocation(world.Pitch, PitchSide.Left, world.Ball.Location)).Return(tacticalLocation);

            Player player = world.AddPlayer(tactics, Vector2D.Zero);
            player.Update(0.1f);

            Assert.AreEqual(tacticalLocation, player.TacticalLocation);
        }

        [Test]
        public void ShouldCalculateTimeToReachBall()
        {
            world.Ball.Kick(new Vector2D(0, 5));
            world.Update(0.1f);
            
            float time = player.TimeToReachBall;

            Assert.IsTrue(1 < time && time < 5);
        }

        [Test]
        public void PlayerCloserToBallShouldNeedLessTimeToReachBall()
        {
            Player player2 = world.AddPlayer(world.Team2, new Vector2D(20, 10));

            world.Ball.Kick(new Vector2D(0, 5));
            world.Update(0.1f);

            Assert.Less(player.TimeToReachBall, player2.TimeToReachBall);
        }

        [Test]
        public void WhenBallIsMovingTowardsPlayerTimeToReachBallShouldReduce()
        {
            world.Update(0.1f);
            float time1 = player.TimeToReachBall;

            world.Ball.Kick(new Vector2D(-2, 0));
            world.Update(0.1f);
            float time2 = player.TimeToReachBall;

            Assert.Less(time2, time1);
        }

        [Test]
        public void PlayerOnBallsPathShouldNeedLessTimeToReachBall()
        {
            Player player2 = world.AddPlayer(world.Team2, new Vector2D(45, 20));

            world.Ball.Kick(new Vector2D(-5, 2));
            world.Update(0.05f);

            Assert.Less(player.TimeToReachBall, player2.TimeToReachBall);
        }

        [Test]
        public void ShouldNotReachBallWhenItsFar()
        {
            Assert.IsFalse(player.CanReachBall);
        }

        [Test]
        public void ShouldReachBallWhenItsClose()
        {
            Player player = world.AddPlayer(world.Team1, world.Ball.Location + new Vector2D(0.2f, -0.2f));

            Assert.IsTrue(player.CanReachBall);
        }

        [Test]
        public void ShouldHaveBall()
        {
            world.Ball.Trap(player);

            Assert.IsTrue(player.HasBall);
        }

        [Test]
        public void ShouldNotHaveBall()
        {
            world.Ball.Trap(player);
            world.Ball.Free();

            Assert.IsFalse(player.HasBall);
        }

        [Test]
        public void ShouldRunToDestination()
        {
            Vector2D destination = new Vector2D(50, 32);
            float tolerance = 2;

            player.Run(destination, tolerance);
            TestHelper.Advance(10, player);

            Assert.Less(player.DistanceTo(destination), 2);
            Assert.IsFalse(player.IsMoving);
        }

        [Test]
        public void ShouldTurnToFaceBall()
        {
            Player player = world.AddPlayer(world.Team2, new Vector2D(15, 80));
            Vector2D directionToBall = player.DirectionTo(world.Ball.Location);

            player.TurnToBall();
            TestHelper.Advance(1, player);

            Assert.AreEqual(directionToBall, player.Heading);
        }

        [Test]
        public void ShouldRunToBallLocation()
        {
            player.RunToBall();
            TestHelper.Advance(5, world);

            Assert.IsTrue(player.CanReachBall);
        }

        [Test]
        public void ShouldNotTrapBallWhenCannotReachIt()
        {
            player.TrapBall();

            Assert.IsFalse(player.HasBall);
        }

        [Test]
        public void ShouldTrapBall()
        {
            player.RunToBall();
            TestHelper.Advance(5, world);
            player.TrapBall();

            Assert.IsTrue(player.HasBall);
        }

        [Test]
        public void ShouldPushBallForwardBeyondItsRange()
        {
            player.RunToBall();
            TestHelper.Advance(5, world);
            player.PushBallForward();
            TestHelper.Advance(1, world);

            Assert.IsFalse(player.CanReachBall);
        }

        [Test]
        public void ShouldPassBall()
        {
            Player other = world.AddPlayer(player.Team, new Vector2D(90, 65));
            Vector2D expectedDirection = world.Ball.DirectionTo(other.Location);

            player.RunToBall();
            TestHelper.Advance(10, world);
            player.TrapBall();
            player.PassBall(other);
            TestHelper.Advance(0.1f, world);

            Assert.IsTrue(world.Ball.Velocity.IsParallelWithSense(expectedDirection));
        }

        [Test]
        public void ShouldFreeBallAfterPassed()
        {
            Player other = world.AddPlayer(player.Team, new Vector2D(90, 65));

            player.RunToBall();
            TestHelper.Advance(10, world);
            player.TrapBall();
            player.PassBall(other);

            Assert.IsFalse(player.HasBall);
        }

        [Test]
        public void ShouldPassBallWithProperSpeed()
        {
            Player other = world.AddPlayer(player.Team, new Vector2D(100, 70));

            player.RunToBall();
            TestHelper.Advance(10, world);
            player.TrapBall();

            player.PassBall(other);
            TestHelper.AdvanceWhile(() => !other.CanReachBall, 100, world);
            
            Assert.GreaterOrEqual(world.Ball.Speed, Player.MinPassReceivingSpeed);
            Assert.LessOrEqual(world.Ball.Speed, Player.MaxPassReceivingSpeed);
        }

        [Test]
        public void ShouldExposePlayersVisibleToHim()
        {
            world.AddPlayer(world.Team1, new Vector2D(5, 14));
            Player other = world.AddPlayer(world.Team1, new Vector2D(20, 1));

            player.Run(new Vector2D(10, 10), 1);
            TestHelper.Advance(100, world);
            player.Turn(new Vector2D(15, 15));
            TestHelper.Advance(100, world);

            IEnumerable<IPlayer> visiblePlayers = player.VisiblePlayersFromOwnTeam;

            Assert.AreSame(other, visiblePlayers.Single());
        }

        [Test]
        public void ShouldCalculateDistanceToOpponentGoal()
        {
            Vector2D goalLocation = world.Pitch.GoalLocation(player.Opponent.Side);
            float expected = player.DistanceTo(goalLocation);

            Assert.AreEqual(expected, player.DistanceToOpponentGoal);
        }

        [Test]
        public void ShouldInformWhenIsFacingOpponentGoal()
        {
            player.Turn(world.Pitch.GoalLocation(player.Opponent.Side));
            TestHelper.Advance(10, world);

            Assert.IsTrue(player.IsFacingOpponentGoal);
        }

        [Test]
        public void ShouldInformWhenIsNotFacingOpponentGoal()
        {
            player.Turn(world.Pitch.GoalLocation(player.Side));
            TestHelper.Advance(10, world);

            Assert.IsFalse(player.IsFacingOpponentGoal);
        }

        [Test]
        public void ShouldShoot()
        {
            player.RunToBall();
            TestHelper.Advance(10, world);
            player.TrapBall();
            player.Shoot();
            TestHelper.Advance(0.1f, world);

            Assert.IsTrue(world.Ball.IsMoving);
        }

        [Test]
        public void ShouldFreeBallAfterShot()
        {
            player.RunToBall();
            TestHelper.Advance(10, world);
            player.TrapBall();
            player.Shoot();
            TestHelper.Advance(0.1f, world);

            Assert.IsFalse(player.HasBall);
        }

        [Test]
        public void ShouldShootInDirectionOfOpponentGoal()
        {
            player.RunToBall();
            TestHelper.Advance(10, world);
            player.TrapBall();
            player.Shoot();
            TestHelper.Advance(0.1f, world);

            Vector2D goalLocation = world.Pitch.GoalLocation(player.Opponent.Side);
            Vector2D directionToGoal = world.Ball.DirectionTo(goalLocation);

            Assert.IsTrue(Vector2D.Equals(world.Ball.Heading, directionToGoal, 0.001f));
        }
    }
}
